feat: 콘텐츠 수집 - 배치 작업 구현#244
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughTMDB와 SportsDB 콘텐츠 수집이 Spring Batch 잡/스텝 기반으로 전환됐습니다. 배치 메타데이터, 잡 실행용 설정, 스케줄러, 재시도·스킵 정책, 리더/프로세서/라이터가 추가됐고, 컨트롤러는 잡을 비동기 실행하도록 바뀌었습니다. 페이지 범위 요청과 스포츠 경기 날짜 입력의 검증이 추가됐으며, 관련 테스트와 예외 응답 처리도 함께 갱신됐습니다. 기존 동기 수집 서비스와 해당 테스트는 제거됐습니다. Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 15
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/test/java/com/codeit/team5/mopl/content/controller/ContentCollectionControllerTest.java (1)
89-112: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueTMDB TV 시리즈 수집 테스트에
startPage유효성 케이스가 빠져 있습니다.TMDB 영화 수집 테스트(Line 69-76)에는
startPage가 0 이하일 때 400을 반환하는 케이스가 있지만, TV 시리즈 수집 테스트에는 동일한 검증 케이스가 없습니다. 두 엔드포인트가 같은PageRangeRequest/@ValidPageRange검증 로직을 공유한다면 치명적인 문제는 아니지만, 대칭적인 커버리지를 유지하면 향후 두 엔드포인트가 서로 다른 검증 규칙을 갖게 되는 회귀를 더 빨리 잡을 수 있습니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/com/codeit/team5/mopl/content/controller/ContentCollectionControllerTest.java` around lines 89 - 112, TMDB TV 시리즈 수집 테스트에 startPage 하한 검증 케이스가 빠져 있습니다. ContentCollectionControllerTest의 collectTmdbTvSeries_* 테스트들에, startPage가 0 이하일 때 /api/admin/contents/collect/tmdb/tv가 400을 반환하는 케이스를 추가해 Movie 수집 테스트와 동일한 PageRangeRequest/@ValidPageRange 커버리지를 맞추세요. 필요하면 collectTmdbTvSeries_missingParams_returnsBadRequest와 collectTmdbTvSeries_endPageLessThanStartPage_returnsBadRequest 패턴을 따라 같은 MockMvc 호출 방식으로 보완하면 됩니다.
♻️ Duplicate comments (1)
src/main/java/com/codeit/team5/mopl/content/batch/processor/TmdbTvSeriesItemProcessor.java (1)
20-71: 📐 Maintainability & Code Quality | 🔵 Trivial
TmdbMovieItemProcessor와의 중복 관련 코멘트를 참고해 주세요.동일한 중복 구조가 이 파일에도 그대로 반복됩니다.
TmdbMovieItemProcessor에 남긴 리팩터링 제안이 이 파일에도 동일하게 적용됩니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/codeit/team5/mopl/content/batch/processor/TmdbTvSeriesItemProcessor.java` around lines 20 - 71, `TmdbTvSeriesItemProcessor` duplicates the same TMDB processing flow already present in `TmdbMovieItemProcessor`, so apply the same refactor here by extracting the shared logic into a common base/helper and reuse it from `process`, `resolveTagNames`, and `buildMetadata`. Keep only TV-series-specific parts in this class, such as `ContentType.TV_SERIES`, `dto.firstAirDate()`, and the TV-specific tag/metadata inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/main/java/com/codeit/team5/mopl/config/BatchConfig.java`:
- Around line 94-96: `TmdbMovieItemReader` is being created as a singleton
`@Bean`, but it keeps mutable step state like `currentPage`, `endPage`,
`totalPages`, and `buffer` initialized via `@BeforeStep`, so concurrent
`asyncJobLauncher` runs can share and overwrite state. Update the reader bean in
`BatchConfig.tmdbMovieItemReader()` to use `@StepScope` so each step gets its
own instance; apply the same scoped pattern to the other stateful readers
(`TmdbTvSeriesItemReader`, `SportsDbEventItemReader`, `SportsDbDayItemReader`)
to prevent cross-run interference.
- Around line 58-64: The async job launcher in BatchConfig should not use
SimpleAsyncTaskExecutor because it can create unbounded threads; switch
asyncJobLauncher to a bounded ThreadPoolTaskExecutor with a configured
concurrency limit and keep the TaskExecutorJobLauncher wiring intact. Also, the
batch readers under content/batch/reader that rely on `@BeforeStep-populated`
mutable fields (such as buffer/currentPage/endPage) should be made step-scoped
by adding `@StepScope` so each step execution gets its own reader instance instead
of sharing singleton state.
In
`@src/main/java/com/codeit/team5/mopl/content/batch/processor/SportsDbEventItemProcessor.java`:
- Around line 29-32: The duplicate check in SportsDbEventItemProcessor is
outside the write boundary, so concurrent runs can still pass the same idEvent
into the writer and create duplicate rows or unique-constraint failures. Fix
this by making the final persistence path in the writer/upsert layer
authoritative: add a database-level unique constraint on (source, external_id)
and handle conflicts with upsert/ignore semantics, or at minimum re-check
against the batch before writing using the existing SportsDbEventItemProcessor
and writer flow. Keep the existing existsBySourceAndExternalId() filter only as
an optimization, not as the source of truth.
In
`@src/main/java/com/codeit/team5/mopl/content/batch/processor/TmdbMovieItemProcessor.java`:
- Around line 20-71: TmdbMovieItemProcessor duplicates the same thumbnail, tag,
and metadata-building logic already present in TmdbTvSeriesItemProcessor, so
extract the shared behavior into a common helper or base abstraction. Move
resolveTagNames, buildMetadata, and thumbnailUrl construction into a reusable
utility or generic processor helper that accepts DTO field accessors (id, title,
genreIds, voteAverage, originalLanguage), then have both TMDB processors
delegate to it to keep movie and TV series handling consistent.
In
`@src/main/java/com/codeit/team5/mopl/content/batch/reader/SportsDbDayItemReader.java`:
- Around line 24-43: 리그 조회 중 하나의 예외가 전체 `beforeStep`을 실패시키며 이미 수집한 `buffer`
데이터까지 잃는 문제가 있습니다. `SportsDbDayItemReader.beforeStep`에서
`sportsDbApiClient.fetchEventsByDay(...)` 호출을 리그 단위로 예외 처리해, 실패한
`SportsDbLeague`만 로그로 남기고 다음 리그를 계속 처리하도록 변경하세요. 성공적으로 받은
`SportsDbEventListResponse`와 `buffer.addAll(events)`는 그대로 유지하되, 예외가 발생한 경우에는 해당
리그 이름과 날짜를 포함해 경고를 남기고 루프를 중단하지 않게 하세요.
In
`@src/main/java/com/codeit/team5/mopl/content/batch/reader/SportsDbEventItemReader.java`:
- Around line 21-45: `SportsDbEventItemReader` and `SportsDbDayItemReader` are
holding mutable `buffer` state inside singleton readers, so they must be made
step-scoped instead of shared beans. Update the Reader bean definitions in
`BatchConfig` to use `@StepScope` so each Step gets a fresh instance, and keep
the `beforeStep`/`read()` flow in each reader unchanged except for removing any
reliance on shared state; do not try to fix this with only `buffer.clear()`.
In
`@src/main/java/com/codeit/team5/mopl/content/batch/reader/TmdbMovieItemReader.java`:
- Around line 54-65: In TmdbMovieItemReader.fetchNextPage(), add an early-exit
path when response.results() is empty so the reader stops instead of continuing
through currentPage until totalPages; update the read loop state in
TmdbMovieItemReader (and any end-of-data flag used there) to mark completion
immediately on a blank TMDB page. Keep the existing buffering logic for
non-empty pages, but make sure the empty-page check happens right after fetching
the TmdbMovieListResponse and before incrementing currentPage or sleeping.
In
`@src/main/java/com/codeit/team5/mopl/content/batch/reader/TmdbTvSeriesItemReader.java`:
- Around line 54-65: The TV series reader currently keeps advancing even when
tmdbApiClient.fetchTvSeries returns an empty page, because fetchNextPage only
fills buffer and increments currentPage. Update TmdbTvSeriesItemReader so
fetchNextPage (or read) detects an empty response.results() and sets the same
early-termination state used by the movie reader, then stop further page
fetching immediately. Make the change in the TmdbTvSeriesItemReader
read/fetchNextPage flow so empty TMDB pages end the batch instead of continuing
to call the API.
In
`@src/main/java/com/codeit/team5/mopl/content/batch/scheduler/ContentCollectionScheduler.java`:
- Around line 42-68: 스케줄 실행 기준 타임존이 명시되지 않아 `@Scheduled` 크론과 `LocalDate.now()`가
JVM 기본 타임존에 의존하는 문제가 있습니다. `ContentCollectionScheduler`의 `runTmdbMovieJob`,
`runTmdbTvSeriesJob`, `runSportsDbDayJob`에서 동일한 `ZoneId`를 사용하도록 `@Scheduled(zone
= "...")`를 지정하고, `LocalDate.now(...)`도 같은 기준으로 맞추세요. 공통 타임존은 상수나 프로퍼티로 분리해 크론과
날짜 파라미터가 항상 같은 기준을 공유하게 하세요.
In
`@src/main/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriter.java`:
- Around line 62-70: `ContentItemWriter`의 썸네일 저장 로직은 `externalId`만 중복 제거한 뒤
`thumbnailUrl`이 같은 항목들을 그대로 `saveAll`하고 있어,
`Collectors.toMap(BinaryContent::getUrl, ...)`에서 동일 URL이 나오면 청크가 실패합니다.
`deduplicatedItems`에서 `thumbnailUrl` 기준으로 한 번 더 중복 제거한 뒤
`binaryContentRepository.saveAll(...)`에 넘기고, `toMap`에는 `BinaryContent::getUrl`
충돌 시 기존 값을 유지하거나 새 값을 선택하는 merge 함수를 추가해 `IllegalStateException`이 나지 않도록 수정하세요.
In
`@src/main/java/com/codeit/team5/mopl/content/controller/ContentCollectionController.java`:
- Around line 102-108: ContentCollectionController.run(...) is swallowing
asyncJobLauncher.run(...) failures, so callers still get a success response even
when the job never starts. Update run(Job, JobParameters) to propagate the
caught JobExecutionAlreadyRunningException, JobRestartException,
JobInstanceAlreadyCompleteException, and JobParametersInvalidException by
rethrowing them or converting them to a ResponseStatusException so the endpoint
failure is visible. Keep the existing logging in
ContentCollectionController.run, but make sure the failure reaches the
controller response path instead of being ignored.
- Around line 89-92: The collectSportsEventsByDay endpoint currently validates
date only with a regex, so invalid calendar dates can still pass through. Update
the ContentCollectionController.collectSportsEventsByDay request parameter to
use LocalDate with `@DateTimeFormat`(iso = ISO.DATE) so both format and real date
validity are enforced, or add a custom validator if the String signature must
remain. Make sure the change is applied on the date parameter in the
collectSportsEventsByDay method while preserving the existing 202 response
behavior for valid inputs.
In
`@src/main/java/com/codeit/team5/mopl/content/dto/request/ValidPageRangeValidator.java`:
- Around line 9-12: The ValidPageRangeValidator class is re-checking null and
minimum-value constraints that are already handled by field validation, causing
duplicate errors. Update isValid(PageRangeRequest, ConstraintValidatorContext)
to only enforce the cross-field rule that endPage must not be less than
startPage, and attach the violation to the endPage field through the
ConstraintValidatorContext instead of returning a generic class-level error.
In
`@src/test/java/com/codeit/team5/mopl/content/batch/reader/TmdbMovieItemReaderTest.java`:
- Around line 78-88: The test in
TmdbMovieItemReaderTest::beforeStep_endPageExceedsMax_clampsTo500 does not
actually verify the clamping behavior because the stubbed
TmdbApiClient.fetchMovies(1) returns totalPages=1, making Math.min(totalPages,
endPage) always resolve to 1. Update the test setup so the initial
TmdbMovieListResponse reports totalPages greater than MAX_PAGE (for example,
1000), then exercise reader.beforeStep(createStepExecution("1", "9999")) and
assert that pages beyond 500 are never requested or that read() returns null
once the 500-page limit is reached. Use the reader, beforeStep, read, and
fetchMovies interactions to confirm the clamp is enforced.
In
`@src/test/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriterTest.java`:
- Around line 46-116: `ContentItemWriterTest` is missing coverage for the tag
flow handled by `ContentItemWriter`. Add tests around `write(...)` using
`ContentWithMetaData` with `tagNames` to verify `tagRepository` is queried,
existing tags are reused when found, new tags are created when absent, and
`ContentTag` links are persisted. Use the existing `contentRepository`,
`tagRepository`, and `contentStatsRepository` mocks to stub and verify the
tag-related behavior alongside the current save/thumbnail assertions.
---
Outside diff comments:
In
`@src/test/java/com/codeit/team5/mopl/content/controller/ContentCollectionControllerTest.java`:
- Around line 89-112: TMDB TV 시리즈 수집 테스트에 startPage 하한 검증 케이스가 빠져 있습니다.
ContentCollectionControllerTest의 collectTmdbTvSeries_* 테스트들에, startPage가 0 이하일 때
/api/admin/contents/collect/tmdb/tv가 400을 반환하는 케이스를 추가해 Movie 수집 테스트와 동일한
PageRangeRequest/@ValidPageRange 커버리지를 맞추세요. 필요하면
collectTmdbTvSeries_missingParams_returnsBadRequest와
collectTmdbTvSeries_endPageLessThanStartPage_returnsBadRequest 패턴을 따라 같은 MockMvc
호출 방식으로 보완하면 됩니다.
---
Duplicate comments:
In
`@src/main/java/com/codeit/team5/mopl/content/batch/processor/TmdbTvSeriesItemProcessor.java`:
- Around line 20-71: `TmdbTvSeriesItemProcessor` duplicates the same TMDB
processing flow already present in `TmdbMovieItemProcessor`, so apply the same
refactor here by extracting the shared logic into a common base/helper and reuse
it from `process`, `resolveTagNames`, and `buildMetadata`. Keep only
TV-series-specific parts in this class, such as `ContentType.TV_SERIES`,
`dto.firstAirDate()`, and the TV-specific tag/metadata inputs.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: dc697a35-86cd-411f-8584-060563c8cf7e
📒 Files selected for processing (34)
build.gradlesrc/main/java/com/codeit/team5/mopl/config/AsyncConfig.javasrc/main/java/com/codeit/team5/mopl/config/BatchConfig.javasrc/main/java/com/codeit/team5/mopl/config/ScheduleConfig.javasrc/main/java/com/codeit/team5/mopl/content/batch/dto/ContentWithMetaData.javasrc/main/java/com/codeit/team5/mopl/content/batch/processor/SportsDbEventItemProcessor.javasrc/main/java/com/codeit/team5/mopl/content/batch/processor/TmdbMovieItemProcessor.javasrc/main/java/com/codeit/team5/mopl/content/batch/processor/TmdbTvSeriesItemProcessor.javasrc/main/java/com/codeit/team5/mopl/content/batch/reader/SportsDbDayItemReader.javasrc/main/java/com/codeit/team5/mopl/content/batch/reader/SportsDbEventItemReader.javasrc/main/java/com/codeit/team5/mopl/content/batch/reader/TmdbMovieItemReader.javasrc/main/java/com/codeit/team5/mopl/content/batch/reader/TmdbTvSeriesItemReader.javasrc/main/java/com/codeit/team5/mopl/content/batch/scheduler/ContentCollectionScheduler.javasrc/main/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriter.javasrc/main/java/com/codeit/team5/mopl/content/client/sportsdb/SportsDbApiClient.javasrc/main/java/com/codeit/team5/mopl/content/controller/ContentCollectionController.javasrc/main/java/com/codeit/team5/mopl/content/controller/api/ContentCollectionApi.javasrc/main/java/com/codeit/team5/mopl/content/dto/external/sportsdb/SportsDbLeague.javasrc/main/java/com/codeit/team5/mopl/content/dto/request/PageRangeRequest.javasrc/main/java/com/codeit/team5/mopl/content/dto/request/ValidPageRange.javasrc/main/java/com/codeit/team5/mopl/content/dto/request/ValidPageRangeValidator.javasrc/main/java/com/codeit/team5/mopl/content/service/SportsDbContentService.javasrc/main/java/com/codeit/team5/mopl/content/service/TmdbContentService.javasrc/main/resources/application.ymlsrc/main/resources/db/migration/V14__add_spring_batch_metadata.sqlsrc/test/java/com/codeit/team5/mopl/content/batch/processor/SportsDbEventItemProcessorTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/processor/TmdbMovieItemProcessorTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/processor/TmdbTvSeriesItemProcessorTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/reader/SportsDbDayItemReaderTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/reader/TmdbMovieItemReaderTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriterTest.javasrc/test/java/com/codeit/team5/mopl/content/controller/ContentCollectionControllerTest.javasrc/test/java/com/codeit/team5/mopl/content/service/SportsDbContentServiceTest.javasrc/test/java/com/codeit/team5/mopl/content/service/TmdbContentServiceTest.java
💤 Files with no reviewable changes (5)
- src/test/java/com/codeit/team5/mopl/content/service/TmdbContentServiceTest.java
- src/test/java/com/codeit/team5/mopl/content/service/SportsDbContentServiceTest.java
- src/main/java/com/codeit/team5/mopl/content/service/SportsDbContentService.java
- src/main/java/com/codeit/team5/mopl/content/service/TmdbContentService.java
- src/main/java/com/codeit/team5/mopl/config/AsyncConfig.java
f3a6591 to
83e0b35
Compare
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/main/java/com/codeit/team5/mopl/config/BatchConfig.java`:
- Around line 90-96: `BatchConfig`의 각 Step 설정에서
`noSkip(WebClientException.class)`가 `SelectiveRetryPolicy`의 상태코드별 판단을 덮어쓰고 있어,
4xx/404 같은 항목 단위 오류까지 전체 Step 실패로 이어질 수 있습니다. `faultTolerant()` 체인에서
`skip()/noSkip()` 구성을 `SelectiveRetryPolicy`와 일치하도록 좁히거나 `TmdbSkipPolicy` 같은 커스텀
`SkipPolicy`로 대체해 `WebClientResponseException`을 상태코드 기준으로 처리하세요. 또한 스킵된 항목을 추적할
수 있도록 각 Step에 `SkipListener`를 등록해 어떤 아이템이 왜 스킵됐는지 로깅하도록 추가하세요.
- Around line 84-97: Move the TMDB external fetches out of the reader read()
path for TmdbMovieItemReader and TmdbTvSeriesItemReader, since HTTP calls and
Thread.sleep() should not run inside the chunk transaction. Follow the pattern
used by SportsDbEventItemReader and SportsDbDayItemReader: preload data in
`@BeforeStep` (or a separate prefetch step) and make read() only return buffered
items via buffer.poll(). Keep BatchConfig’s tmdbMovieStep wiring the same, but
ensure these reader classes no longer do network I/O or waiting during read().
In
`@src/main/java/com/codeit/team5/mopl/content/batch/exception/BatchJobLaunchException.java`:
- Around line 6-11: `BatchJobLaunchException` currently only accepts `jobName`,
so the original failure cause cannot be preserved; update the exception
hierarchy to support cause chaining consistently. Add a `Throwable`-aware
constructor or equivalent support in `BusinessException`, then have
`BatchJobLaunchException` pass the underlying cause through instead of dropping
it. Align this with the existing `initCause(cause)` pattern used by other
exceptions in the same hierarchy so callers can trace the root error from
`BatchJobLaunchException` and `BusinessException`.
In
`@src/main/java/com/codeit/team5/mopl/content/batch/retry/SelectiveRetryPolicy.java`:
- Line 21: Replace the hardcoded 429 magic number in SelectiveRetryPolicy with
the standard Spring HttpStatus.TOO_MANY_REQUESTS value, and update any related
retry checks that reference the TOO_MANY_REQUESTS constant. Use the HttpStatus
enum directly in the policy logic so the intent is explicit and the code is less
error-prone; keep the change localized to the retry status comparison and any
helper methods in SelectiveRetryPolicy that depend on that constant.
In
`@src/main/java/com/codeit/team5/mopl/content/batch/scheduler/ContentCollectionScheduler.java`:
- Around line 72-79: The `run(Job job, JobParameters params)` catch block in
`ContentCollectionScheduler` is only logging `e.getMessage()`, so update the
`log.error` call to include the full exception object and stack trace, matching
the behavior used in `ContentCollectionController.run()`. Keep the same error
context with `job.getName()`, but pass the caught exception itself as the final
argument so scheduler failures are easier to diagnose.
In
`@src/test/java/com/codeit/team5/mopl/content/controller/ContentCollectionControllerTest.java`:
- Around line 140-148: Add a test in ContentCollectionControllerTest for the
invalid league enum path so the new MethodArgumentTypeMismatchException handling
is covered. In collectSportsEvents_success’s соседний SportsDB request flow,
create a case that calls the same /api/admin/contents/collect/sports endpoint
with an invalid league value and asserts a 400 Bad Request, verifying the
SportsDbLeague parameter parsing failure is wired to the global exception
handler.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 72324186-6207-4c2c-b017-29b2ae59ecf5
📒 Files selected for processing (45)
build.gradlesrc/main/java/com/codeit/team5/mopl/config/AsyncConfig.javasrc/main/java/com/codeit/team5/mopl/config/BatchConfig.javasrc/main/java/com/codeit/team5/mopl/config/ScheduleConfig.javasrc/main/java/com/codeit/team5/mopl/content/batch/dto/ContentWithMetaData.javasrc/main/java/com/codeit/team5/mopl/content/batch/exception/BatchJobLaunchException.javasrc/main/java/com/codeit/team5/mopl/content/batch/processor/SportsDbEventItemProcessor.javasrc/main/java/com/codeit/team5/mopl/content/batch/processor/TmdbMovieItemProcessor.javasrc/main/java/com/codeit/team5/mopl/content/batch/processor/TmdbTvSeriesItemProcessor.javasrc/main/java/com/codeit/team5/mopl/content/batch/reader/SportsDbDayItemReader.javasrc/main/java/com/codeit/team5/mopl/content/batch/reader/SportsDbEventItemReader.javasrc/main/java/com/codeit/team5/mopl/content/batch/reader/TmdbMovieItemReader.javasrc/main/java/com/codeit/team5/mopl/content/batch/reader/TmdbTvSeriesItemReader.javasrc/main/java/com/codeit/team5/mopl/content/batch/retry/SelectiveRetryPolicy.javasrc/main/java/com/codeit/team5/mopl/content/batch/scheduler/ContentCollectionScheduler.javasrc/main/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriter.javasrc/main/java/com/codeit/team5/mopl/content/client/sportsdb/SportsDbApiClient.javasrc/main/java/com/codeit/team5/mopl/content/controller/ContentCollectionController.javasrc/main/java/com/codeit/team5/mopl/content/controller/api/ContentCollectionApi.javasrc/main/java/com/codeit/team5/mopl/content/dto/external/sportsdb/SportsDbLeague.javasrc/main/java/com/codeit/team5/mopl/content/dto/request/PageRangeRequest.javasrc/main/java/com/codeit/team5/mopl/content/dto/request/ValidPageRange.javasrc/main/java/com/codeit/team5/mopl/content/dto/request/ValidPageRangeValidator.javasrc/main/java/com/codeit/team5/mopl/content/service/SportsDbContentService.javasrc/main/java/com/codeit/team5/mopl/content/service/TmdbContentService.javasrc/main/java/com/codeit/team5/mopl/global/dto/suggestion/ErrorResponseSuggestion.javasrc/main/java/com/codeit/team5/mopl/global/exception/GlobalExceptionHandler.javasrc/main/resources/application.ymlsrc/main/resources/db/migration/V14__add_spring_batch_metadata.sqlsrc/test/java/com/codeit/team5/mopl/content/batch/processor/SportsDbEventItemProcessorTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/processor/TmdbMovieItemProcessorTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/processor/TmdbTvSeriesItemProcessorTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/reader/SportsDbDayItemReaderTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/reader/TmdbMovieItemReaderTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/reader/TmdbTvSeriesItemReaderTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/retry/SelectiveRetryPolicyTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriterTest.javasrc/test/java/com/codeit/team5/mopl/content/controller/ContentCollectionControllerTest.javasrc/test/java/com/codeit/team5/mopl/content/controller/ContentControllerTest.javasrc/test/java/com/codeit/team5/mopl/content/dto/request/ValidCursorValidatorTest.javasrc/test/java/com/codeit/team5/mopl/content/dto/request/ValidPageRangeValidatorTest.javasrc/test/java/com/codeit/team5/mopl/content/service/SportsDbContentServiceTest.javasrc/test/java/com/codeit/team5/mopl/content/service/TmdbContentServiceTest.javasrc/test/java/com/codeit/team5/mopl/content/service/util/ContentCollectionUtilsTest.javasrc/test/java/com/codeit/team5/mopl/user/controller/UserControllerIntegrationTest.java
💤 Files with no reviewable changes (4)
- src/test/java/com/codeit/team5/mopl/content/service/TmdbContentServiceTest.java
- src/main/java/com/codeit/team5/mopl/content/service/SportsDbContentService.java
- src/main/java/com/codeit/team5/mopl/content/service/TmdbContentService.java
- src/test/java/com/codeit/team5/mopl/content/service/SportsDbContentServiceTest.java
- TMDB 영화/TV 시리즈 ItemReader 추가 (페이지네이션 + buffer 패턴) - SportsDB 경기 이벤트 ItemReader 추가 (단일 요청 + buffer 패턴)
- TMDB 영화/TV 시리즈 ItemProcessor 추가 - SportsDB 경기 이벤트 ItemProcessor 추가 - Processor 출력 DTO ContentWithMetaData 추가
- Content, ContentStats 일괄 저장 (saveAll) - chunk 단위 태그 일괄 조회/저장으로 DB 쿼리 최소화 - 외부 URL 썸네일 저장 처리
- 청크 사이즈 100으로 설정 - 재시도 3회, 2초 간격 - 스킵 최대 10건 허용
- SportsDbApiClient에 fetchEventsByDay() 추가 - SportsDbDayItemReader 추가 (10개 리그별 당일 경기 수집) - BatchConfig에 sportsDbDayJob 추가 - SportsDbLeague Enum에 시즌 목록 추가
- ContentCollectionScheduler 추가 - yml파일 tmdb.batch.daily-end-page 설정값 추가
- EnableScheduling을 ScheduleConfig로 분리 - AsyncConfig 삭제 (contentCollectionExecutor 미사용) - BatchConfig에 컨트롤러 전용 asyncJobLauncher 빈 추가
- ContentCollectionController를 JobLauncher 기반으로 리팩터링 - TmdbContentService, SportsDbContentService 및 관련 테스트 삭제 # Conflicts: # src/test/java/com/codeit/team5/mopl/content/controller/ContentCollectionControllerTest.java
- PageRangeRequest DTO와 ValidPageRange 커스텀 어노테이션으로 검증 (400 반환) - Pattern으로 season 파라미터 YYYY-YYYY 형식 강제 (400 반환) - ContentCollectionApi Swagger 문서에 400 응답 케이스 추가 - 검증 케이스 테스트 추가 (페이지 범위 위반, season 형식 오류)
- SportsDbDayItemReader가 Job 파라미터 date를 사용하도록 변경 - 컨트롤러에 /sports/day 엔드포인트 추가 (날짜 직접 지정하여 수집 가능) - ContentCollectionApi Swagger 문서에 /sports/day 엔드포인트 추가
- startPage, endPage 타입을 Integer로 변경하고 NotNull 추가 - ValidPageRangeValidator에 null 가드 추가 - ContentCollectionControllerTest 수정
- SimpleAsyncTaskExecutor 대신 ThreadPoolTaskExecutor로 교체 - 큐 포화로 인한 TaskRejectedException을 503으로 매핑 - MDC만 복사하는 TaskDecorator 적용 - TMDB/SportsDB ItemReader 4종에 @StepScope 적용 - ContentCollectionScheduler의 JobLauncher 필드명 통일
- date 파라미터를 LocalDate + @DateTimeFormat(ISO.DATE)로 변경 - MethodArgumentTypeMismatchException 핸들러 메서드 추가 - 실존하지 않는 날짜(2024-02-31)에 대한 400 응답 검증 테스트 추가
- SportsDbDayItemReader: 리그 하나가 실패해도 나머지 리그 데이터는 유지 - SelectiveRetryPolicy: 5xx/429/네트워크/DB 일시 오류만 재시도, 나머지 4xx는 제외 - 배치 4개 Step에 공통 적용
- results가 비어있으면 totalPages와 무관하게 즉시 수집을 종료해 불필요한 페이지 호출을 방지 - TmdbTvSeriesItemReaderTest 추가 (영화 테스트와 동일)
- startPage>=1 중복 체크 제거 (Min(1)이 이미 담당) - endPage<startPage 위반을 endPage 필드에 귀속시켜 응답에 노출되도록 수정 - ValidPageRangeValidatorTest 추가
- totalPages를 낮게 스텁해 클램핑 여부와 무관하게 항상 통과하던 문제 수정
…rollerIntegrationTest 수정
- 페이지별 재시도/실패 시 건너뛰기 로직 추가 + 재시도 정책 공통화
- ExternalFailureClassifier로 재시도/스킵 판단 기준을 공유 - SelectiveSkipPolicy: 시스템 전역 문제는 스킵하지 않고 Step을 즉시 실패, 항목의 문제는 skipLimit 내에서 스킵하도록 세분화 - LoggingSkipListener: 스킵된 항목 read/process/write 단계별 로깅
83e0b35 to
7e1cf5e
Compare
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/test/java/com/codeit/team5/mopl/user/controller/UserControllerIntegrationTest.java (1)
223-232: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win상태 코드만 검증하고 응답 바디 검증이 빠져 있습니다.
500 → 400으로 기대값을 수정한 것 자체는 타당한 방향입니다 (UUID 파싱 실패는 클라이언트 입력 오류이므로 400이 맞습니다). 다만 이 파일의 다른 유사 테스트들(
createUser_missingName_returnsBadRequest등)은$.exceptionType,$.message까지 함께 검증하는데, 이 테스트는 상태 코드만 확인하고 있어 검증 강도가 다른 테스트와 비교해 다소 약합니다. 전역 예외 핸들러가 의도한 예외 타입으로 정확히 매핑되는지까지 확인하려면 바디 검증을 추가하는 게 좋습니다.🔧 제안 예시
mockMvc.perform(get("/api/users/{userId}", "invalid-uuid") .with(authentication(authOf(UUID.randomUUID())))) - .andExpect(status().isBadRequest()); + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.exceptionType").exists()) + .andExpect(jsonPath("$.message").exists());🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/com/codeit/team5/mopl/user/controller/UserControllerIntegrationTest.java` around lines 223 - 232, `getUser_invalidUuid` in `UserControllerIntegrationTest` only checks the HTTP status, so strengthen it to match the other bad-request tests in this class by asserting the error response body as well. Update the `mockMvc.perform(...)` expectations to verify the same error payload fields used elsewhere (for example `$.exceptionType` and `$.message`) so the `UserController`/global exception handling for invalid UUIDs is validated end to end.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/java/com/codeit/team5/mopl/content/batch/processor/TmdbMovieItemProcessor.java`:
- Around line 52-58: `TmdbMovieItemProcessor.resolveTagNames` currently assumes
`dto.genreIds()` is always non-null, which can NPE when TMDB omits `genre_ids`.
Add a null-safe default at the DTO level by giving `genreIds` a default empty
list in `TmdbMovieDto`, and apply the same safeguard to the similarly shaped
`TmdbTvDto` to prevent the issue from recurring. Keep `resolveTagNames` focused
on mapping IDs to labels, relying on the DTOs to always return a non-null list.
In
`@src/main/java/com/codeit/team5/mopl/content/batch/processor/TmdbTvSeriesItemProcessor.java`:
- Around line 52-58: `TmdbTvSeriesItemProcessor.resolveTagNames` assumes
`dto.genreIds()` is non-null, so add null handling before streaming to avoid NPE
and skipped batch items. Normalize a null genre list to an empty list inside
`resolveTagNames()` or set a default `List.of()` on `TmdbTvDto`, and make the
same null-safe adjustment in the analogous movie processor so both processors
follow the same pattern.
In
`@src/main/java/com/codeit/team5/mopl/content/batch/reader/SportsDbEventItemReader.java`:
- Around line 19-40: `SportsDbEventItemReader.beforeStep`에서
`sportsDbApiClient.fetchEventsBySeason` 호출이 재시도 없이 바로 실행되는 문제가 있습니다.
`SportsDbDayItemReader`, `TmdbMovieItemReader`, `TmdbTvSeriesItemReader`와 동일하게
`RetryTemplate`을 생성자에 주입하고, `beforeStep` 내부의 `fetchEventsBySeason`를
`retryTemplate.execute(...)`로 감싸서 일시적 오류 시 재시도되도록 수정하세요. 또한
`BatchConfig.sportsDbEventItemReader()` 빈 생성자 호출에도 `retryTemplate` 인자를 추가해 연결되게
하세요.
In
`@src/main/java/com/codeit/team5/mopl/content/batch/reader/TmdbTvSeriesItemReader.java`:
- Around line 19-75: `TmdbTvSeriesItemReader` duplicates nearly the same paging
and buffering flow as `TmdbMovieItemReader`, so extract the shared logic into a
common abstract reader to avoid drift. Create a generic base such as
`AbstractTmdbPagedItemReader<T>` that owns the `beforeStep` loop,
`RetryTemplate` call, buffer management, and logging, then let
`TmdbTvSeriesItemReader` and `TmdbMovieItemReader` provide only the page fetch
method and item validity check (for example, name/title non-blank). Keep the
existing reader class names and `read()` behavior, but move the shared
page-processing code into the base class.
In
`@src/main/java/com/codeit/team5/mopl/content/batch/scheduler/ContentCollectionScheduler.java`:
- Around line 72-79: run(Job job, JobParameters params) in
ContentCollectionScheduler only catches four Spring Batch exceptions, so
unexpected failures from asyncJobLauncher.run() can bypass the
scheduler-specific error log. Broaden the catch in run() to include general
Exception (while keeping the existing batch-specific exceptions) and log the job
name plus the throwable with the same "[Scheduler] Job 실행 실패" context so any
runtime failure is captured consistently.
In
`@src/main/java/com/codeit/team5/mopl/content/controller/ContentCollectionController.java`:
- Around line 77-90: The season parameter validation in
ContentCollectionController.collectSportsEvents currently only checks the
YYYY-YYYY format, so unsupported seasons can still reach the job; replace or
augment the `@Pattern` validation with a custom validator that checks the
requested season against SportsDbLeague.getSeasons() for the selected league.
Update the request handling so invalid seasons are rejected at the controller
boundary with a 400 before run(...) is called, and keep the validation logic
tied to SportsDbLeague so season list changes only need one update point.
In
`@src/main/java/com/codeit/team5/mopl/content/dto/external/sportsdb/SportsDbLeague.java`:
- Around line 19-20: `SportsDbLeague`의 `SEASONS`가 하드코딩된 문자열 목록이라 매년 수동 갱신이 필요하니,
현재 연도를 기준으로 최신 시즌을 동적으로 생성하는 헬퍼로 바꾸고 `getSeasons()`가 실제로 사용되는지 함께 확인하세요.
`SEASONS` 선언과 `getSeasons()` 주변을 수정해 SportsDB 지원 범위에 맞는 시즌 목록을 계산하도록 정리하고, 소비처가
없다면 제거하거나 향후 `/sports` 시즌 검증용으로 연결될 의도를 명확히 해주세요.
In
`@src/test/java/com/codeit/team5/mopl/content/batch/processor/TmdbMovieItemProcessorTest.java`:
- Around line 83-97: The test in TmdbMovieItemProcessorTest is too weak because
it only checks that tagNames is non-empty, not that the mapped genre label is
correct. Update process_validGenreId_tagNamesIncluded to assert the exact
expected tag value returned by TmdbMovieItemProcessor.resolveTagNames(), using
TmdbMovieGenre.fromId(18L).getLabel() with containsExactly so the test verifies
the specific mapping and stays consistent with SportsDbEventItemProcessorTest.
In
`@src/test/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriterTest.java`:
- Line 127: The test uses a fully qualified Mockito call for never() even though
the file already imports never statically, creating inconsistent style in
ContentItemWriterTest. Update the verify(...) call in the
binaryContentRepository assertion to use the same imported never() form used
elsewhere in the file, keeping the notation consistent with the other Mockito
verifications.
---
Outside diff comments:
In
`@src/test/java/com/codeit/team5/mopl/user/controller/UserControllerIntegrationTest.java`:
- Around line 223-232: `getUser_invalidUuid` in `UserControllerIntegrationTest`
only checks the HTTP status, so strengthen it to match the other bad-request
tests in this class by asserting the error response body as well. Update the
`mockMvc.perform(...)` expectations to verify the same error payload fields used
elsewhere (for example `$.exceptionType` and `$.message`) so the
`UserController`/global exception handling for invalid UUIDs is validated end to
end.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: b8323e46-0c51-4d06-8357-c96cadafefb9
📒 Files selected for processing (49)
build.gradlesrc/main/java/com/codeit/team5/mopl/config/AsyncConfig.javasrc/main/java/com/codeit/team5/mopl/config/BatchConfig.javasrc/main/java/com/codeit/team5/mopl/config/ScheduleConfig.javasrc/main/java/com/codeit/team5/mopl/content/batch/dto/ContentWithMetaData.javasrc/main/java/com/codeit/team5/mopl/content/batch/exception/BatchJobLaunchException.javasrc/main/java/com/codeit/team5/mopl/content/batch/processor/SportsDbEventItemProcessor.javasrc/main/java/com/codeit/team5/mopl/content/batch/processor/TmdbMovieItemProcessor.javasrc/main/java/com/codeit/team5/mopl/content/batch/processor/TmdbTvSeriesItemProcessor.javasrc/main/java/com/codeit/team5/mopl/content/batch/reader/SportsDbDayItemReader.javasrc/main/java/com/codeit/team5/mopl/content/batch/reader/SportsDbEventItemReader.javasrc/main/java/com/codeit/team5/mopl/content/batch/reader/TmdbMovieItemReader.javasrc/main/java/com/codeit/team5/mopl/content/batch/reader/TmdbTvSeriesItemReader.javasrc/main/java/com/codeit/team5/mopl/content/batch/retry/ExternalFailureClassifier.javasrc/main/java/com/codeit/team5/mopl/content/batch/retry/LoggingSkipListener.javasrc/main/java/com/codeit/team5/mopl/content/batch/retry/SelectiveRetryPolicy.javasrc/main/java/com/codeit/team5/mopl/content/batch/retry/SelectiveSkipPolicy.javasrc/main/java/com/codeit/team5/mopl/content/batch/scheduler/ContentCollectionScheduler.javasrc/main/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriter.javasrc/main/java/com/codeit/team5/mopl/content/client/sportsdb/SportsDbApiClient.javasrc/main/java/com/codeit/team5/mopl/content/controller/ContentCollectionController.javasrc/main/java/com/codeit/team5/mopl/content/controller/api/ContentCollectionApi.javasrc/main/java/com/codeit/team5/mopl/content/dto/external/sportsdb/SportsDbLeague.javasrc/main/java/com/codeit/team5/mopl/content/dto/request/PageRangeRequest.javasrc/main/java/com/codeit/team5/mopl/content/dto/request/ValidPageRange.javasrc/main/java/com/codeit/team5/mopl/content/dto/request/ValidPageRangeValidator.javasrc/main/java/com/codeit/team5/mopl/content/service/SportsDbContentService.javasrc/main/java/com/codeit/team5/mopl/content/service/TmdbContentService.javasrc/main/java/com/codeit/team5/mopl/global/dto/suggestion/ErrorResponseSuggestion.javasrc/main/java/com/codeit/team5/mopl/global/exception/GlobalExceptionHandler.javasrc/main/resources/application.ymlsrc/main/resources/db/migration/V14__add_spring_batch_metadata.sqlsrc/test/java/com/codeit/team5/mopl/content/batch/processor/SportsDbEventItemProcessorTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/processor/TmdbMovieItemProcessorTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/processor/TmdbTvSeriesItemProcessorTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/reader/SportsDbDayItemReaderTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/reader/TmdbMovieItemReaderTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/reader/TmdbTvSeriesItemReaderTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/retry/SelectiveRetryPolicyTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/retry/SelectiveSkipPolicyTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriterTest.javasrc/test/java/com/codeit/team5/mopl/content/controller/ContentCollectionControllerTest.javasrc/test/java/com/codeit/team5/mopl/content/controller/ContentControllerTest.javasrc/test/java/com/codeit/team5/mopl/content/dto/request/ValidCursorValidatorTest.javasrc/test/java/com/codeit/team5/mopl/content/dto/request/ValidPageRangeValidatorTest.javasrc/test/java/com/codeit/team5/mopl/content/service/SportsDbContentServiceTest.javasrc/test/java/com/codeit/team5/mopl/content/service/TmdbContentServiceTest.javasrc/test/java/com/codeit/team5/mopl/content/service/util/ContentCollectionUtilsTest.javasrc/test/java/com/codeit/team5/mopl/user/controller/UserControllerIntegrationTest.java
💤 Files with no reviewable changes (4)
- src/test/java/com/codeit/team5/mopl/content/service/SportsDbContentServiceTest.java
- src/test/java/com/codeit/team5/mopl/content/service/TmdbContentServiceTest.java
- src/main/java/com/codeit/team5/mopl/content/service/SportsDbContentService.java
- src/main/java/com/codeit/team5/mopl/content/service/TmdbContentService.java
- SportsDbEventItemReaderTest 추가
- SportsDbLeague의 미사용 SEASONS/getSeasons() 제거 - TmdbMovieItemProcessorTest: tagNames 정확한 값 검증
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/main/java/com/codeit/team5/mopl/content/controller/ContentCollectionController.java (1)
73-86: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
season값이 실제 지원 시즌인지 검증되지 않습니다.
@Pattern(regexp = "^\\d{4}-\\d{4}$")는 형식만 검사할 뿐, 해당 리그가 실제로 지원하는 시즌인지는 확인하지 않습니다. 예를 들어 특정 리그가 2015-2016 시즌 데이터를 제공하지 않는데도 형식만 맞으면 그대로sportsDbEventJob이 실행되고, 결과적으로 빈 데이터 수집이나 외부 API의 불필요한 호출로 이어질 수 있습니다.
SportsDbLeague에 이미getSeasons()가 존재한다면, 이를 활용한 커스텀 검증기(@ValidSeason같은)로 묶어 컨트롤러 진입 시점에 400으로 막아주는 편이 좋습니다. 형식 검증만 유지하면 구현은 단순하지만, 지원 시즌 목록이 바뀔 때마다 이 규칙과 실제 데이터가 어긋날 위험이 남습니다.♻️ 제안 방향
- `@Pattern`(regexp = "^\\d{4}-\\d{4}$", message = "시즌 형식은 YYYY-YYYY이어야 합니다. (예: 2023-2024)") - `@RequestParam` String season + `@RequestParam` String season그리고 메서드 본문 초입에서
league.getSeasons().contains(season)여부를 확인하거나,PageRangeRequest의ValidPageRange처럼 클래스 레벨 커스텀 애노테이션 검증기를 추가하는 방식을 고려해보세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/codeit/team5/mopl/content/controller/ContentCollectionController.java` around lines 73 - 86, 컨트롤러의 collectSportsEvents는 season 형식만 검증하고 실제로 SportsDbLeague가 지원하는 시즌인지 확인하지 않으므로, 요청 진입 시점에 지원 시즌 검증을 추가해야 합니다. SportsDbLeague의 getSeasons()를 사용해 season이 목록에 포함되는지 검사하거나, `@ValidSeason` 같은 커스텀 검증기로 묶어 collectSportsEvents에서 실행 전에 400으로 막도록 수정하세요. 검증 대상은 league와 season 조합이며, sportsDbEventJob 실행 전에 실패 처리되도록 연결하면 됩니다.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/java/com/codeit/team5/mopl/content/batch/scheduler/ContentCollectionScheduler.java`:
- Around line 68-74: `ContentCollectionScheduler.run(Job, JobParameters)` is
duplicating the same `asyncJobLauncher.run(...)` plus failure-handling pattern
used in `ContentCollectionController.run(Job, JobParameters)`. Extract the
shared batch execution flow into a common helper/component such as
`BatchJobRunner`, and let both callers delegate to it while preserving their
different failure behavior via a flag or callback (scheduler logs-and-swallow,
controller wraps in `BatchJobLaunchException`). Keep the logging/error handling
centralized so future changes to `asyncJobLauncher` invocation or error
formatting only need to be made once.
---
Duplicate comments:
In
`@src/main/java/com/codeit/team5/mopl/content/controller/ContentCollectionController.java`:
- Around line 73-86: 컨트롤러의 collectSportsEvents는 season 형식만 검증하고 실제로
SportsDbLeague가 지원하는 시즌인지 확인하지 않으므로, 요청 진입 시점에 지원 시즌 검증을 추가해야 합니다.
SportsDbLeague의 getSeasons()를 사용해 season이 목록에 포함되는지 검사하거나, `@ValidSeason` 같은 커스텀
검증기로 묶어 collectSportsEvents에서 실행 전에 400으로 막도록 수정하세요. 검증 대상은 league와 season 조합이며,
sportsDbEventJob 실행 전에 실패 처리되도록 연결하면 됩니다.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: e2a5050a-d87a-4320-9daf-08eb3ef1657b
📒 Files selected for processing (11)
src/main/java/com/codeit/team5/mopl/config/BatchConfig.javasrc/main/java/com/codeit/team5/mopl/content/batch/reader/SportsDbEventItemReader.javasrc/main/java/com/codeit/team5/mopl/content/batch/scheduler/ContentCollectionScheduler.javasrc/main/java/com/codeit/team5/mopl/content/controller/ContentCollectionController.javasrc/main/java/com/codeit/team5/mopl/content/dto/external/sportsdb/SportsDbLeague.javasrc/main/java/com/codeit/team5/mopl/content/dto/external/tmdb/TmdbMovieDto.javasrc/main/java/com/codeit/team5/mopl/content/dto/external/tmdb/TmdbTvDto.javasrc/test/java/com/codeit/team5/mopl/content/batch/processor/TmdbMovieItemProcessorTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/processor/TmdbTvSeriesItemProcessorTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/reader/SportsDbEventItemReaderTest.javasrc/test/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriterTest.java
💤 Files with no reviewable changes (1)
- src/main/java/com/codeit/team5/mopl/content/dto/external/sportsdb/SportsDbLeague.java
관련 이슈
작업 내용
변경 사항
테스트 내용
체크리스트
리뷰 포인트
스크린샷 / 참고 자료
🔗 관련 이슈
#측정하지 않음📝 작업 내용
기존에는 TMDB/SportsDB 콘텐츠 수집이 서비스 메서드 호출 흐름에 직접 묶여 있어, 대량 수집 시 외부 API 재시도/부분 실패 처리, 배치 메타데이터 관리, 스케줄러 실행 안정성, 그리고 입력값 검증을 일관된 방식으로 적용하기 어려웠습니다.
이에 따라 “외부 데이터 읽기 ↔ DB 저장” 책임을 배치 파이프라인으로 분리하고, 잡 단위로 재시도/스킵/부분 성공을 제어할 수 있도록 Spring Batch 기반 수집 구조로 전환했습니다. 또한 수동(관리자) 초기 적재도 배치 잡 파라미터 기반으로 정리해, 스케줄러 실행과 동일한 검증/실행 모델을 갖추도록 했습니다.
주요 변경사항
구조/책임의 가장 큰 변화: 서비스 호출 기반 수집 → 배치 잡/스텝 기반 실행
Job/Step으로 분리했습니다.데이터 흐름, 예외 처리, API 계약 변경
@Async서비스 호출 대신JobLauncher로 배치를 제출하도록 변경했습니다.startPage/endPage:PageRangeRequest+ValidPageRange로endPage >= startPage검증season:YYYY-YYYY형식 강제/sports/day: ISO 날짜(LocalDate) 바인딩faultTolerant()와 선별적 retry/skip 정책(SelectiveRetryPolicy,SelectiveSkipPolicy)을 적용했고, 외부 호출이 “일시적(transient)”일 때만 재시도하도록 분기했습니다.externalId기준 중복 제거를 수행하고, Processor에서도 기존 저장 여부를 확인해 스킵하도록 구성했습니다.Asia/Seoul기준 매일 3:00 AM대에 TMDB/SportsDB 일별 잡들을 트리거하도록 추가했으며, (리뷰 후 적용된) TMDB “빈 페이지 조기 종료”, 스레드 포화/차단 방지 방향의 수정이 반영되었습니다.리뷰 시 주의/트레이드오프
ExternalFailureClassifier에 의존하므로, “재시도 대상” 판정 로직의 정확성/일관성이 운영 성격상 중요합니다(측정하지 않음).📊 변경 효과 요약 (가능한 경우에만)
V16__add_spring_batch_metadata.sql)spring.batch.job.enabled: false로 기본 자동 실행을 비활성화하여, 스케줄러/컨트롤러가 의도한 방식으로만 잡이 실행되도록 안전장치를 마련했습니다.